package cppcloud

import (
	"encoding/json"
	"fmt"
	"net"
	"strings"
	"sync"
	"time"
	"tool"
)

/** tcp 连接类
 */

type TCPCli struct {
	cli        net.Conn
	skipWhoami bool
	timeoutSec time.Duration
	err        error
	connLock   *sync.Mutex

	svraddr   string
	cliIP     string
	svrid     int // 这个是服务端为本连接分配置的ID, 由whoami请求获得
	clitype   int // = 300 # 默认普通go应用
	mconf     string
	svrname   string // = "unknow"
	desc      string
	tag       string
	aliasname string

	attr   map[string]interface{}
	ntfMgr tool.NotifyMgr

	// 统计信息
	stat MsgStat
}

// Maketcpcli  constructor
func MakeTCPCli(svrAddr string, attrMap map[string]interface{}, timoSec int) (ret *TCPCli) {
	timeOutDuration := time.Second * time.Duration(timoSec)
	conn, err := net.DialTimeout("tcp", svrAddr, timeOutDuration)
	if nil != err {
		fmt.Println("Dial to ", svrAddr, "fail", err)
		return nil
	}

	cliAddr := conn.LocalAddr().String()
	ip := strings.Split(cliAddr, ":")[0]
	skipWhoami := false
	if skip, ok := attrMap["skipWhoami"]; ok {
		skipWhoami, _ = skip.(bool)
	}

	ret = &TCPCli{
		cli: conn, svraddr: svrAddr, cliIP: ip,
		skipWhoami: skipWhoami,
		timeoutSec: timeOutDuration,
		attr:       make(map[string]interface{}),
		connLock:   new(sync.Mutex),
		ntfMgr:     tool.NewNotifyMgr(),
	}
	ret.setAttr("clitype", 300)
	ret.setAttr("svrname", "unknow")

	ret.setAttrMap(attrMap)

	if !ret.reqWhoAmI() {
		ret.close()
		ret = nil
	}
	return
}

// 设置客户端属性
func (tcpcli *TCPCli) setAttrMap(mapAttr map[string]interface{}) {
	for key, val := range mapAttr {
		tcpcli.setAttr(key, val)
	}
}

// 设置客户端属性
func (tcpcli *TCPCli) setAttr(key string, val interface{}) {
	tcpcli.attr[key] = val
	intVal, ok := val.(int)

	if ok {
		switch key {
		case "svrid":
			tcpcli.svrid = intVal
		case "clitype":
			tcpcli.clitype = intVal
		case "timeoutSec":
			tcpcli.clitype = intVal
		}
		return
	}

	strVal, ok1 := val.(string)
	if ok1 {
		switch key {
		case "mconf":
			tcpcli.mconf = strVal
		case "svraddr":
			tcpcli.svraddr = strVal
		case "svrname":
			tcpcli.svrname = strVal
		case "desc":
			tcpcli.desc = strVal
		case "tag":
			tcpcli.tag = strVal
		case "aliasname":
			tcpcli.aliasname = strVal
		}
	}
}

func (tcpcli *TCPCli) checkConnect() bool {
	tcpcli.connLock.Lock()         // 获取锁，加锁
	defer tcpcli.connLock.Unlock() // 锁可用,解锁

	if nil != tcpcli.err {
		tcpcli.close()
		tcpcli.cli, tcpcli.err = net.DialTimeout("tcp", tcpcli.svraddr, tcpcli.timeoutSec)
		if nil == tcpcli.err {
			return tcpcli.reqWhoAmI()
		}
	}

	return nil == tcpcli.err
}

func (tcpcli *TCPCli) reqWhoAmI() bool {
	if tcpcli.skipWhoami {
		return true
	}

	nSend := tcpcli.sendMap(CMD_WHOAMI_REQ, 1, tcpcli.attr)
	if nSend > 0 && nil == tcpcli.err { // 发送成功
		cmdid, seqid, msg, err := tcpcli.recv()
		fmt.Println("WHOAMI_RESP| ", cmdid, seqid, msg, err)
		if nil != err || len(msg) < 5 {
			return false
		}
		rspMap := make(map[string]interface{})
		if nil == json.Unmarshal([]byte(msg), &rspMap) {
			code0, ok := tool.JSONGetInt(rspMap, "code")
			if ok && 0 == code0 {
				tcpcli.setAttrMap(rspMap)
				tcpcli.invokeCallBack("reconnect_ok", nil)
				return true
			}
		}
	}
	return false
}

func (tcpcli *TCPCli) close() {
	if nil != tcpcli.cli {
		tcpcli.cli.Close()
		tcpcli.cli = nil
	}
}

func (tcpcli *TCPCli) sendMap(cmdid uint16, seqid uint16, mp map[string]interface{}) (ret int) {
	ret = -1
	jmsg, err := json.Marshal(mp)
	if nil != err {
		fmt.Println("JSON-MARSHAL-INVALID|", err)
		return
	}

	return tcpcli.sendByteArr(cmdid, seqid, jmsg)
}

func (tcpcli *TCPCli) sendString(cmdid uint16, seqid uint16, body string) (ret int) {
	return tcpcli.sendByteArr(cmdid, seqid, []byte(body))
}

func (tcpcli *TCPCli) sendByteArr(cmdid uint16, seqid uint16, body []byte) (ret int) {
	if !tcpcli.checkConnect() {
		return -2
	}

	ret, tcpcli.err = tcpcli.cli.Write(ToBytes(cmdid, seqid, body))
	if nil != tcpcli.err {
		tcpcli.close()
	} else {
		tcpcli.stat.SendBytes += int64(ret)
		tcpcli.stat.SendPkgn += 1
	}
	return
}

func (tcpcli *TCPCli) recv() (cmdid uint16, seqid uint16, msg string, err error) {

	if !tcpcli.checkConnect() {
		err = &MsgError{"connect fail"}
		return
	}

	cmdid, seqid, msg, err = RecvMessage(tcpcli.cli, &tcpcli.stat.RecvBytes)
	if nil != err {
		tcpcli.close()
	} else {
		tcpcli.stat.RecvPkgn += 1
	}

	return
}

func (tcpcli *TCPCli) SvrName() string {
	return tcpcli.svrname
}

func (tcpcli *TCPCli) CliIP() string {
	return tcpcli.cliIP
}

func (tcpcli *TCPCli) Svrid() int {
	return tcpcli.svrid
}

func RecvMessage(cli net.Conn, recvBytes *int64) (cmdid uint16, seqid uint16, msg string, err error) {

	bHeader := make([]byte, HEADLEN)
	for i := 0; i < HEADLEN; { // 接收报文头部
		nbyte, readErr := cli.Read(bHeader[i:])
		if nil != readErr {
			err = readErr
			return
		}
		i += nbyte
		*recvBytes += int64(nbyte)
	}

	cmdid0, seqid0, bodyLen, parseErr := ParseHead(bHeader)
	if nil != parseErr {
		err = parseErr
		//tcppvd.close()
		return
	}
	if bodyLen > MAXBODY_LEN {
		err = &MsgError{"bodyLen too large"}
		//tcppvd.close()
		return
	}

	bodyMsg := make([]byte, bodyLen)
	for i := 0; i < int(bodyLen); { // 接收报文头部
		nbyte, readErr := cli.Read(bodyMsg[i:])
		if nil != readErr {
			err = readErr
			//tcppvd.close()
			return
		}
		i += nbyte
		*recvBytes += int64(nbyte)
	}

	//recvPkgn += 1
	return cmdid0, seqid0, string(bodyMsg), nil
}

// AddNotifyCallBack 注册通告回调
func (tcpcli *TCPCli) AddNotifyCallBack(name string, cb tool.NotifyCB) {
	tcpcli.ntfMgr.AddNotifyCallBack(name, cb)
}

// invokeCallBack 发起通告回调
func (tcpcli *TCPCli) invokeCallBack(name string,
	param map[string]interface{}) (code int, result interface{}) {
	code, result = tcpcli.ntfMgr.InvokeCallBack(name, param)
	return
}
